/*
 * selectList jQuery plugin
 * version 0.1
 *
 * Copyright (c) 2009 Michal Wojciechowski (odyniec.net)
 *
 * Dual licensed under the MIT (MIT-LICENSE.txt) 
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://odyniec.net/projects/selectlist/
 *
 */

(function($) {

$.selectList = {
    init : function(select, options) {
        var $list, $item, $newItem, $option, keyEvent, ready,
            first = 0, change, click, keypress, enter;
        
        function show($item, callback) {
            if (options.addAnimate && ready)
                if (typeof options.addAnimate == 'function')
                    options.addAnimate($item.hide().get(0), callback);
                else
                    $item.hide().fadeIn(300, callback);
            else {
                $item.show();
                if (callback)
                    callback.call($item.get(0));
            }
        }

        function hide($item, callback) {
            if (options.removeAnimate && ready)
                if (typeof options.removeAnimate == 'function')
                    options.removeAnimate($item.get(0), callback);
                else
                    $item.fadeOut(300, callback);
            else {
                $item.hide();
                if (callback)
                    callback.call($item.get(0));
            }
        }

        function cmp(item1, item2) {
            if (typeof options.sort == 'function')
                return options.sort(item1, item2);
            else
                return ($(item1).data('text') > $(item2).data('text'))
                    == (options.sort != 'desc');
        }

        function add(value, text, callHandler) {
            $option = null;
            
            if ($(value).is('option')) {
                $option = $(value);

                if ($option.get(0).index == first-1)
                    return;

                if (!options.duplicates)
                    $option.attr('disabled', 'disabled')
                        .data('disabled', true);
                
                value = $option.val();
                text = $option.text();
            }

            $newItem = $(options.template.replace(/%text%/g, text)
                    .replace(/%value%/g, value)).hide();

            $('<input type="hidden" />').appendTo($newItem)
                    .attr({name:$(select).attr('name'),value:value});

            $newItem.data('value', value).data('text', text);
            if ($option)
                $newItem.data('option', $option);
            
            $newItem.addClass(options.classPrefix + '-item');

            if (options.clickRemove)
                $newItem.click(function () {
                    remove($(this));
                });

            var callback = function () {
                if (callHandler !== false)
                    options.onAdd(select, value, text);           
            };
            
            if (options.sort && ($item = $list.children().eq(0)).length) {
                while ($item.length && cmp($newItem.get(0), $item.get(0)))
                    $item = $item.next();

                if ($item.length)
                    show($newItem.insertBefore($item), callback);
                else
                    show($newItem.appendTo($list), callback);
            } else
                show($newItem.appendTo($list), callback);
        }
        
        function remove($item, callHandler) {
            hide($item, function () {
                var value = $(this).data('value'),
                    text = $(this).data('text');
                
                if ($(this).data('option'))
                    $(this).data('option').removeAttr('disabled')
                        .removeData('disabled');
                
                $(this).remove();
                if (callHandler !== false)
                    options.onRemove(select, value, text);
            });
        }
        
        this.add = function (value, text) {
            add(value, text);
        };
        
        this.remove = function (value) {
            $list.children().each(function () {
                if ($(this).data('value') == value)
                    remove($(this));
            });
        };

        this.setOptions = function (newOptions) {
            var sort = newOptions.sort && newOptions.sort != options.sort;

            options = $.extend(options, newOptions);

            if (sort)
                $list.children().slice(first).each(function () {
                    add($(this).data('value'), $(this).data('text'), false);
                }).remove();
        };

        this.setOptions(options = $.extend( {
            addAnimate: true,
            classPrefix: 'selectlist',
            clickRemove: true,
            removeAnimate: true,
            template: '<li>%text%</li>',
            onAdd: function () {},
            onRemove: function () {}
        }, options));
        
        ($list = $(options.list || $('<ul />').insertAfter($(select))))
            .addClass(options.classPrefix + '-list');

        $(select).children(':selected').each(function () {
            add($(this), null, false);
        });

        $(select).removeAttr('multiple');
        
        if ($(select).attr('title')) {
            $(select).prepend($('<option />').text($(select).attr('title')))
                .get(0).selectedIndex = 0;
            first = 1;
        }

        keyEvent = $.browser.msie || $.browser.safari ? 'keydown' : 'keypress';
        
        $(select).bind(keyEvent, function (event) {
            keypress = true;
            
            if ((event.keyCode || event.which) == 13) {
                enter = true;
                $(select).change();
                keypress = true;
                return false;
            }
        })
        .change(function() {
            if (!keypress && !click) return;
            change = true;
            $option = $(select).find('option:selected');
            if (!$option.data('disabled') && (!keypress || enter))
                add($option);
            
            if (keypress)
                keypress = change = click = false;
            
            enter = false;
        })
        .mousedown(function () {
            click = true;
        });
        
        $(select).find('option').click(function (event) {
            if ($.browser.mozilla && event.pageX >= $(select).offset().left &&
                    event.pageX <= $(select).offset().left + $(select).outerWidth() &&
                    event.pageY >= $(select).offset().top &&
                    event.pageY <= $(select).offset().top + $(select).outerHeight())
                return false;
            
            click = true;
            
            if (!$(this).attr('disabled') && !$(this).data('disabled')
                    && !keypress && !change) 
                add($(this));
            
            if (!keypress)
                change = click = false;
            
            return false;
        });
        
        $(select.form).submit(function () {
            $(select).attr('disabled', 'disabled');
        });
        
        $(window).bind('beforeunload', function () {
            $(select).removeAttr('name');
        });
        
        ready = true;
    }
};

$.fn.selectList = function(options) {
    options = options || {};

    this.filter('select').each(function () {
        if ($(this).data('selectList'))
            $(this).data('selectList').setOptions(options);
        else
            $(this).data('selectList', new $.selectList.init(this, options));
    });

    if (options.instance)
        return $(this).filter('select').data('selectList');

    return this;
};

})(jQuery);
